package com.cats.bi.sqltool.basic;


import com.cats.bi.sqltool.SQLString;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * {@link} https://github.com/Yeamy/SqlBuilder
 *
 * @author wang
 */
public abstract class AbstractClause extends AbstractTableColumn<AbstractClause> {

    public static final String SYNTAX_LIKE = " LIKE ";
    public static final String SYNTAX_NOT_LIKE = " NOT LIKE ";
    protected Object column;

    protected void appendColumn(StringBuilder sb) {
        if (column instanceof AbstractColumn) {
            ((AbstractColumn) column).toSql(sb);
        } else {
            SQLString.appendColumn(sb, column.toString());
        }
    }

    @Override
    public void signTable(Map<Object, AbstractTableColumn<?>> tables) {
        if (column instanceof AbstractTableColumn) {
            ((AbstractTableColumn) column).signTable(tables);
        }
    }

    @Override
    public void unSignTable(Map<Object, AbstractTableColumn<?>> tables) {
        if (column instanceof AbstractTableColumn) {
            ((AbstractTableColumn) column).unSignTable(tables);
        }
    }

    @Override
    public void tableInFrom(StringBuilder sb) {
        if (column instanceof AbstractTableColumn) {
            ((AbstractTableColumn) column).tableInFrom(sb);
        }
    }

    private static class SimpleAbstractClause extends AbstractClause {
        protected String pattern;

        SimpleAbstractClause(AbstractColumn<?> column, String pattern) {
            this.column = column;
            this.pattern = pattern;
        }

        SimpleAbstractClause(String column, String pattern) {
            this.column = column;
            this.pattern = pattern;
        }

        @Override
        protected void subSQL(StringBuilder sb) {
            appendColumn(sb);
            sb.append(pattern);
        }
    }

    private static class NormalAbstractClause extends AbstractClause {
        protected String cal;
        protected Object pattern;

        NormalAbstractClause(AbstractColumn<?> column, String cal, Object pattern) {
            this.column = column;
            this.cal = cal;
            this.pattern = pattern;
        }

        NormalAbstractClause(String column, String cal, Object pattern) {
            this.column = column;
            this.cal = cal;
            this.pattern = pattern;
        }

        @Override
        protected void subSQL(StringBuilder sb) {
            appendColumn(sb);
            sb.append(cal);
            SQLString.appendValue(sb, pattern);
        }

        @Override
        public void signTable(Map<Object, AbstractTableColumn<?>> tables) {
            super.signTable(tables);
            if (pattern instanceof AbstractTableColumn) {
                ((AbstractTableColumn) pattern).signTable(tables);
            }
        }

        @Override
        public void unSignTable(Map<Object, AbstractTableColumn<?>> tables) {
            super.unSignTable(tables);
            if (pattern instanceof AbstractTableColumn) {
                ((AbstractTableColumn) pattern).unSignTable(tables);
            }
        }
    }

    private static class AbstractClauseIn extends AbstractClause {
        private Object[] array;
        private int[] intArray;
        private String cal;
        static final String IN = " IN(";
        static final String NOT_IN = " NOT IN(";

        AbstractClauseIn(AbstractColumn<?> column, String cal, Object[] array) {
            this.column = column;
            this.array = array;
            this.cal = cal;
        }

        AbstractClauseIn(String column, String cal, Object[] array) {
            this.column = column;
            this.array = array;
            this.cal = cal;
        }

        AbstractClauseIn(AbstractColumn<?> column, String cal, int[] intArray) {
            this.column = column;
            this.intArray = intArray;
            this.cal = cal;
        }

        AbstractClauseIn(String column, String cal, int[] intArray) {
            this.column = column;
            this.intArray = intArray;
            this.cal = cal;
        }

        @Override
        protected void subSQL(StringBuilder sb) {
            appendColumn(sb);
            sb.append(cal);
            boolean f = true;
            if (array != null) {
                for (Object li : array) {
                    if (f) {
                        f = false;
                    } else {
                        sb.append(", ");
                    }
                    SQLString.appendValue(sb, li);
                }
            } else {
                for (int li : intArray) {
                    if (f) {
                        f = false;
                    } else {
                        sb.append(", ");
                    }
                    SQLString.appendValue(sb, li);
                }
            }
            sb.append(')');
        }
    }

    private static class AbstractClauseBetween extends AbstractClause {
        private Object start;
        private Object end;

        AbstractClauseBetween(AbstractColumn<?> column, Object start, Object end) {
            this.column = column;
            this.start = start;
            this.end = end;
        }

        AbstractClauseBetween(String column, Object start, Object end) {
            this.column = column;
            this.start = start;
            this.end = end;
        }

        @Override
        protected void subSQL(StringBuilder sb) {
            appendColumn(sb);
            sb.append(" BETWEEN ");
            SQLString.appendValue(sb, start);
            sb.append(" AND ");
            SQLString.appendValue(sb, end);
        }

        @Override
        public void signTable(Map<Object, AbstractTableColumn<?>> tables) {
            super.signTable(tables);
            if (start instanceof AbstractTableColumn) {
                ((AbstractTableColumn) start).signTable(tables);
            }
            if (end instanceof AbstractTableColumn) {
                ((AbstractTableColumn) end).signTable(tables);
            }
        }

        @Override
        public void unSignTable(Map<Object, AbstractTableColumn<?>> tables) {
            super.unSignTable(tables);
            if (start instanceof AbstractTableColumn) {
                ((AbstractTableColumn) start).unSignTable(tables);
            }
            if (end instanceof AbstractTableColumn) {
                ((AbstractTableColumn) end).unSignTable(tables);
            }
        }
    }

    private static class AbstractClauseNotBetween extends AbstractClause {
        private Object start;
        private Object end;

        AbstractClauseNotBetween(AbstractColumn<?> column, Object start, Object end) {
            this.column = column;
            this.start = start;
            this.end = end;
        }

        AbstractClauseNotBetween(String column, Object start, Object end) {
            this.column = column;
            this.start = start;
            this.end = end;
        }

        @Override
        protected void subSQL(StringBuilder sb) {
            appendColumn(sb);
            sb.append(" NOT BETWEEN ");
            SQLString.appendValue(sb, start);
            sb.append(" AND ");
            SQLString.appendValue(sb, end);
        }

        @Override
        public void signTable(Map<Object, AbstractTableColumn<?>> tables) {
            super.signTable(tables);
            if (start instanceof AbstractTableColumn) {
                ((AbstractTableColumn) start).signTable(tables);
            }
            if (end instanceof AbstractTableColumn) {
                ((AbstractTableColumn) end).signTable(tables);
            }
        }

        @Override
        public void unSignTable(Map<Object, AbstractTableColumn<?>> tables) {
            super.unSignTable(tables);
            if (start instanceof AbstractTableColumn) {
                ((AbstractTableColumn) start).unSignTable(tables);
            }
            if (end instanceof AbstractTableColumn) {
                ((AbstractTableColumn) end).unSignTable(tables);
            }
        }
    }

    private static class AbstractClauseRaw extends AbstractClause {
        private String raw;

        AbstractClauseRaw(String raw) {
            this.raw = raw;
        }

        @Override
        protected void subSQL(StringBuilder sb) {
            sb.append(raw);
        }
    }

    public static AbstractClause andAll(List<AbstractClause> list) {
        switch (list.size()) {
            case 0:
                return null;
            case 1:
                return list.get(0);
            default:
                MultiClause clause = null;
                for (AbstractClause li : list) {
                    if (clause == null) {
                        clause = new MultiClause(li);
                    } else {
                        clause.and(li);
                    }
                }
                return clause;
        }
    }

    public static AbstractClause orAll(List<AbstractClause> list) {
        switch (list.size()) {
            case 0:
                return null;
            case 1:
                return list.get(0);
            default:
                MultiClause clause = null;
                for (AbstractClause li : list) {
                    if (clause == null) {
                        clause = new MultiClause(li);
                    } else {
                        clause.or(li);
                    }
                }
                return clause;
        }
    }
    // ---------------------------------------------------------------------------

    // IS NULL
    public static AbstractClause isNull(AbstractColumn<?> column) {
        return new SimpleAbstractClause(column, " IS NULL");
    }

    // IS NOT NULL
    public static AbstractClause isNotNull(AbstractColumn<?> column) {
        return new SimpleAbstractClause(column, " IS NOT NULL");
    }

    // = 等于
    public static AbstractClause eq(AbstractColumn<?> column, Object pattern) {
        return new NormalAbstractClause(column, " = ", pattern);
    }

    // <> 不等于
    public static AbstractClause notEqual(AbstractColumn<?> column, Object pattern) {
        return new NormalAbstractClause(column, " <> ", pattern);
    }

    // > 大于
    public static AbstractClause greaterThan(AbstractColumn<?> column, Object pattern) {
        return new NormalAbstractClause(column, " > ", pattern);
    }

    // < 小于
    public static AbstractClause lessThan(AbstractColumn<?> column, Object pattern) {
        return new NormalAbstractClause(column, " < ", pattern);
    }

    // >= 大于等于
    public static AbstractClause greaterEqual(AbstractColumn<?> column, Object pattern) {
        return new NormalAbstractClause(column, " >= ", pattern);
    }

    // <= 小于等于
    public static AbstractClause lessEqual(AbstractColumn<?> column, Object pattern) {
        return new NormalAbstractClause(column, " <= ", pattern);
    }

    // LIKE 搜索某种模式
    public static AbstractClause like(AbstractColumn<?> column, String pattern) {
        return new NormalAbstractClause(column, SYNTAX_LIKE, pattern);
    }

    public static AbstractClause contains(AbstractColumn<?> column, String pattern) {
        return like(column, '%' + pattern + '%');
    }

    public static AbstractClause startWith(AbstractColumn<?> column, String pattern) {
        return like(column, pattern + '%');
    }

    public static AbstractClause endWith(AbstractColumn<?> column, String pattern) {
        return like(column, '%' + pattern);
    }

    // NOT LIKE
    public static AbstractClause notLike(Column column, String pattern) {
        return new NormalAbstractClause(column, SYNTAX_NOT_LIKE, pattern);
    }

    public static AbstractClause notContains(Column column, String pattern) {
        return new NormalAbstractClause(column, SYNTAX_NOT_LIKE, '%' + pattern + '%');
    }

    public static AbstractClause notStartWith(Column column, String pattern) {
        return new NormalAbstractClause(column, SYNTAX_NOT_LIKE, pattern + '%');
    }

    public static AbstractClause notEndWith(Column column, String pattern) {
        return new NormalAbstractClause(column, SYNTAX_NOT_LIKE, '%' + pattern);
    }

    // IN
    public static AbstractClause in(AbstractColumn<?> column, int... array) {
        return new AbstractClauseIn(column, AbstractClauseIn.IN, array);
    }

    public static AbstractClause in(AbstractColumn<?> column, Object... pattern) {
        return new AbstractClauseIn(column, AbstractClauseIn.IN, pattern);
    }

    public static AbstractClause in(AbstractColumn<?> column, Collection<?> pattern) {
        return new AbstractClauseIn(column, AbstractClauseIn.IN, pattern.toArray());
    }

    public static AbstractClause in(AbstractColumn<?> column, Select pattern) {
        return new NormalAbstractClause(column, " IN ", pattern);
    }

    // NOT IN
    public static AbstractClause notIn(AbstractColumn<?> column, int... array) {
        return new AbstractClauseIn(column, AbstractClauseIn.NOT_IN, array);
    }

    public static AbstractClause notIn(AbstractColumn<?> column, Object... pattern) {
        return new AbstractClauseIn(column, AbstractClauseIn.NOT_IN, pattern);
    }

    public static AbstractClause notIn(AbstractColumn<?> column, Collection<?> pattern) {
        return new AbstractClauseIn(column, AbstractClauseIn.NOT_IN, pattern.toArray());
    }

    public static AbstractClause notIn(AbstractColumn<?> column, Select pattern) {
        return new NormalAbstractClause(column, " NOT IN ", pattern);
    }

    // BETWEEN 在某个范围内
    public static AbstractClause between(AbstractColumn<?> column, Object start, Object end) {
        return new AbstractClauseBetween(column, start, end);
    }

    // NOT BETWEEN 不在某个范围内
    public static AbstractClause notBetween(AbstractColumn<?> column, Object start, Object end) {
        return new AbstractClauseNotBetween(column, start, end);
    }

    // ---------------------------------------------------------------------------

    // IS NULL
    public static AbstractClause isNull(String column) {
        return new SimpleAbstractClause(column, " IS NULL");
    }

    // IS NOT NULL
    public static AbstractClause isNotNull(String column) {
        return new SimpleAbstractClause(column, " IS NOT NULL");
    }

    // = 等于
    public static AbstractClause eq(String column, Object pattern) {
        return new NormalAbstractClause(column, " = ", pattern);
    }

    // <> 不等于
    public static AbstractClause notEqual(String column, Object pattern) {
        return new NormalAbstractClause(column, " <> ", pattern);
    }

    // > 大于
    public static AbstractClause greaterThan(String column, Object pattern) {
        return new NormalAbstractClause(column, " > ", pattern);
    }

    // < 小于
    public static AbstractClause lessThan(String column, Object pattern) {
        return new NormalAbstractClause(column, " < ", pattern);
    }

    // >= 大于等于
    public static AbstractClause greaterEqual(String column, Object pattern) {
        return new NormalAbstractClause(column, " >= ", pattern);
    }

    // <= 小于等于
    public static AbstractClause lessEqual(String column, Object pattern) {
        return new NormalAbstractClause(column, " <= ", pattern);
    }

    // LIKE 搜索某种模式
    public static AbstractClause like(String column, String pattern) {
        return new NormalAbstractClause(column, SYNTAX_LIKE, pattern);
    }

    public static AbstractClause contains(String column, String pattern) {
        return new NormalAbstractClause(column, SYNTAX_LIKE, '%' + pattern + '%');
    }

    public static AbstractClause startWith(String column, String pattern) {
        return new NormalAbstractClause(column, SYNTAX_LIKE, pattern + '%');
    }

    public static AbstractClause endWith(String column, String pattern) {
        return new NormalAbstractClause(column, SYNTAX_LIKE, '%' + pattern);
    }

    // IN
    public static AbstractClause in(String column, int... array) {
        return new AbstractClauseIn(column, AbstractClauseIn.IN, array);
    }

    public static AbstractClause in(String column, Object... pattern) {
        return new AbstractClauseIn(column, AbstractClauseIn.IN, pattern);
    }

    public static AbstractClause in(String column, Collection<?> pattern) {
        return new AbstractClauseIn(column, AbstractClauseIn.IN, pattern.toArray());
    }

    public static AbstractClause in(String column, Select pattern) {
        return new NormalAbstractClause(column, " IN ", pattern);
    }

    // NOT IN
    public static AbstractClause notIn(String column, int... array) {
        return new AbstractClauseIn(column, AbstractClauseIn.NOT_IN, array);
    }

    public static AbstractClause notIn(String column, Object... pattern) {
        return new AbstractClauseIn(column, AbstractClauseIn.NOT_IN, pattern);
    }

    public static AbstractClause notIn(String column, Collection<?> pattern) {
        return new AbstractClauseIn(column, AbstractClauseIn.NOT_IN, pattern.toArray());
    }

    public static AbstractClause notIn(String column, Select pattern) {
        return new NormalAbstractClause(column, " NOT IN ", pattern);
    }

    // BETWEEN 在某个范围内
    public static AbstractClause between(String column, Object start, Object end) {
        return new AbstractClauseBetween(column, start, end);
    }

    public static AbstractClause parse(String sql) {
        return new AbstractClauseRaw(sql);
    }


    // Multi ----------------------------------------------------------------------

    private static class ClauseLi {
        AbstractClause abstractClause;
        String logic;

        private ClauseLi(AbstractClause abstractClause, String logic) {
            this.abstractClause = abstractClause;
            this.logic = logic;
        }
    }

    private ArrayList<ClauseLi> clauses;

    protected boolean isMulti() {
        return clauses != null && !clauses.isEmpty();
    }

    public AbstractClause and(AbstractClause abstractClause) {
        if (abstractClause == null) {
            return this;
        }
        if (clauses == null) {
            clauses = new ArrayList<>();
        }
        clauses.add(new ClauseLi(abstractClause, " AND "));
        return this;
    }

    public AbstractClause or(AbstractClause abstractClause) {
        if (abstractClause == null) {
            return this;
        }
        if (clauses == null) {
            clauses = new ArrayList<>();
        }
        clauses.add(new ClauseLi(abstractClause, " OR "));
        return this;
    }

    public AbstractClause multiWithLogic(AbstractClause abstractClause, String pattern) {
        if (abstractClause == null) {
            return this;
        }
        if (clauses == null) {
            clauses = new ArrayList<>();
        }
        clauses.add(new ClauseLi(abstractClause, pattern));
        return this;
    }

    @Override
    public final void toSql(StringBuilder sql) {
        subSQL(sql);
        if (isMulti()) {
            for (ClauseLi li : clauses) {
                sql.append(li.logic);
                AbstractClause abstractClause = li.abstractClause;
                if (abstractClause.isMulti()) {
                    sql.append('(');
                    abstractClause.toSql(sql);
                    sql.append(')');
                } else {
                    abstractClause.toSql(sql);
                }
            }
        }
    }

    protected abstract void subSQL(StringBuilder sql);

}
